package cn.org.itool.ws.framework.netty.channel;


import cn.org.itool.common.constent.CodeEnum;
import cn.org.itool.ws.framework.config.NettyProperties;
import cn.org.itool.ws.framework.event.CloseEvent;
import cn.org.itool.ws.framework.netty.auth.Principal;
import cn.org.itool.ws.framework.utils.ChannelUtils;
import cn.org.itool.ws.framework.ws.WsServer;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.log4j.Log4j2;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 服务器端心跳处理
 *
 */
@Log4j2
@ChannelHandler.Sharable
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
    private static final NettyProperties NETTY_PROPERTIES = WsServer.NETTY_PROPERTIES;

    private final int maxTimeoutCount;
    private final AtomicInteger timeoutCount = new AtomicInteger();

    /**
     * 构造{@link HeartbeatHandler}
     */
    public HeartbeatHandler(){
        if (NETTY_PROPERTIES.getIdlCloseSec() < 0){
            this.maxTimeoutCount = -1;
        }else{
            int max =  NETTY_PROPERTIES.getIdlCloseSec() / NETTY_PROPERTIES.getIdleSec();
            if (NETTY_PROPERTIES.getIdlCloseSec() % NETTY_PROPERTIES.getIdleSec() != 0){
                max = max + 1;
            }
            this.maxTimeoutCount = max;
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent event){
            if(event.state() == IdleState.READER_IDLE){
                timeoutHandle(ctx, event);
            }
        }else {
            super.userEventTriggered(ctx, evt);
        }
    }

    /**
     * 超时处理
     *
     * @param ctx {@link ChannelHandlerContext}
     * @param event {@link IdleStateEvent}
     */
    private void timeoutHandle(ChannelHandlerContext ctx, IdleStateEvent event){
        final int count = getTimeoutCount(event);
        if(isTimeout(count)){

            Optional<Principal> optional = ChannelUtils.getPrincipalOpt(ctx.channel());
            optional.ifPresent(e -> {
                try{
                    String wsId = ctx.channel().id().asLongText();
                    ctx.fireUserEventTriggered(new CloseEvent(e, wsId));
                }catch (Exception ex){
                    log.error("e:", ex);
                }
            });

            if(ctx.channel().isActive()){
                ChannelUtils.close(ctx.channel(), CodeEnum.TIMEOUT_OFFLINE);
                log.info("Channel timeout close channelId={}, count={}", ctx.channel().id().asLongText(), count);
            }
        }
    }

    /**
     * 得到超时次数
     *
     * @param event {@link IdleStateEvent}
     * @return 超时次数
     */
    private int getTimeoutCount(IdleStateEvent event){
        if(!event.isFirst()){
            return timeoutCount.incrementAndGet();
        }

        int count = 1;
        timeoutCount.set(count);
        return count;
    }

    /**
     * 是否超时
     *
     * @param count 超时次数
     * @return true 已经超时
     */
    private boolean isTimeout(int count){
        return maxTimeoutCount > 0 && count >= maxTimeoutCount;
    }
}
